﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows;
using System.Windows.Interop;
using Interop;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Input;
using System.Windows.Controls;

using ShellAnywhere.Model;
using System.IO;

namespace ShellAnywhere.ViewModel
{
	using IActionToken = IToken<IAction>;

	public class ShellAnywhereViewModel : DependencyObject
	{
		#region empty
		public const string SHELL_ANYWHERE_PROMPT = "ShellAnywhere>";
		public const string EMPTY_PROMPT = "";
		public ActionViewModel EmptyActionViewModel
		{
			get { return new ActionViewModel(Prompt); }
		}
		#endregion empty

		#region ctors
		public ShellAnywhereViewModel()
		{
			Actions = new ObservableCollection<ActionViewModel>();
			Output = new ObservableCollection<OutputViewModel>();
			_Posture = Posture.Launch;
		}
		public ShellAnywhereViewModel(IShellAnywhereModel model) : this()
		{
			_Model = model;
			_Model.ActionsChanged += OnActionsChanged;
			_Model.OutputAppend += OnOutputAppend;
			_Model.ActionMgr.CurrentDirectoryChanged += OnCurrentDirectoryChanged;
			_Model.Init();
		}
		~ShellAnywhereViewModel()
		{
			_Model.End();
		}
		#endregion ctors

		#region props
		public string UserInput
		{
			get { return _Model.ActionMgr.UserInput; }
			set { _Model.ActionMgr.UserInput = value; }
		}
		private Posture _LastPosture;
		private Posture _Posture;
		public Posture Posture
		{
			get { return _Posture; }
			set
			{
				if (_Posture != value)
					OnPostureChanged(_Posture = value);
			}
		}
		public void OnPostureChanged(Posture posture)
		{
			_Model.Posture = posture;
			switch (posture)
			{
				case Posture.Hidden:
					WindowVisibility = Visibility.Hidden;
					//_KeyboardHook.MessageForwarding = MessageForwarding.None;
					_HiddenTime = DateTime.Now;
					Prompt = SHELL_ANYWHERE_PROMPT;
					break;
				case Posture.Launch:
					OutputVisibility = Visibility.Collapsed;
					WindowVisibility = Visibility.Visible;
					Prompt = SHELL_ANYWHERE_PROMPT;
					break;
				case Posture.Shell:
					WindowVisibility = OutputVisibility = Visibility.Visible;
					Prompt = SHELL_ANYWHERE_PROMPT;
					break;
				case Posture.Navigation:
					Prompt = CreatePrompt(_Model.ActionMgr.CurrentDirectory);
					break;
			}
		}

		public ObservableCollection<ActionViewModel> Actions { get; set; }
		public ObservableCollection<OutputViewModel> Output { get; set; }

		public Visibility WindowVisibility
		{
			get { return (Visibility)GetValue(WindowVisibilityProperty); }
			set { SetValue(WindowVisibilityProperty, value); }
		}
		public Visibility OutputVisibility
		{
			get { return (Visibility)GetValue(OutputVisibilityProperty); }
			set { SetValue(OutputVisibilityProperty, value); }
		}
		public string Prompt
		{
			get { return (string)GetValue(PromptProperty); }
			set { SetValue(PromptProperty, value); }
		}
		private static readonly DependencyProperty WindowVisibilityProperty = DependencyProperty.Register("WindowVisibility", typeof(Visibility), typeof(ShellAnywhereViewModel), new UIPropertyMetadata(Visibility.Visible));
		private static readonly DependencyProperty OutputVisibilityProperty = DependencyProperty.Register("OutputVisibility", typeof(Visibility), typeof(ShellAnywhereViewModel), new UIPropertyMetadata(Visibility.Collapsed));
		private static readonly DependencyProperty PromptProperty = DependencyProperty.Register("Prompt", typeof(string), typeof(ShellAnywhereViewModel), new UIPropertyMetadata("ShellAnywhere>"));
		#endregion props

		#region methods
		public void OnOutputAppend(Brush color, string line)
		{
			if (!string.IsNullOrEmpty(line) && Output != null)
				Output.Add(new OutputViewModel(color, line));
		}
		private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
		{
			char c;
			KeyEventArgs kea = null;
			switch ((Win32KeyboardCodes)msg)
			{
				case Win32KeyboardCodes.WM_CHAR:
				case Win32KeyboardCodes.WM_SYSCHAR:
					switch (c = (char)wParam)
					{
					    case '\b':
					        kea = new KeyEventArgs(Keyboard.PrimaryDevice, _ActiveSource, 0, Key.Back);
							kea.RoutedEvent = UIElement.KeyDownEvent;
					        OnAnyKeyDown(this, kea);
					        handled = kea.Handled;
					        break;
					    case '\r':
					        kea = new KeyEventArgs(Keyboard.PrimaryDevice, _ActiveSource, 0, Key.Return);
							kea.RoutedEvent = UIElement.KeyDownEvent;
					        OnAnyKeyDown(this, kea);
					        handled = kea.Handled;
					        break;
					    case '\t':
					        kea = new KeyEventArgs(Keyboard.PrimaryDevice, _ActiveSource, 0, Key.Tab);
							kea.RoutedEvent = UIElement.KeyDownEvent;
					        OnAnyKeyDown(this, kea);
					        handled = kea.Handled;
					        break;
					    default:
							if (Convert.ToInt32(c) == 27) //ESCAPE
							{
								kea = new KeyEventArgs(Keyboard.PrimaryDevice, _ActiveSource, 0, Key.Escape);
								kea.RoutedEvent = UIElement.KeyDownEvent;
								OnAnyKeyDown(this, kea);
								handled = kea.Handled;
								break;
							}
					        UserInput += c;
					        handled = true;
					        break;
					}
					break;
			}
			return IntPtr.Zero;
		}
		private void OnAnyKeyDown(object sender, KeyEventArgs e)
		{
			//set to true; if default switch reached it will be changed to false; e.Handled set afer the switch statment
			bool handled = true; 
			switch (e.Key)
			{
				case Key.Escape:
					Posture = Posture.Hidden;
					break;
				case Key.Left:
				case Key.Back:
					_Model.ActionMgr.Withdraw();
					break;
				case Key.Right:
				case Key.Enter:
					_Model.ActionMgr.Advance();
					break;
				case Key.Up:
					if (_Model.ActionMgr.HasActions)
						_Model.ActionMgr.DecrIndex();
					else
						UserInput = _Model.HistoryMgr.DecrIndex();
					break;
				case Key.Down:
					if (_Model.ActionMgr.HasActions)
						_Model.ActionMgr.IncrIndex();
					else
						UserInput = _Model.HistoryMgr.IncrIndex();
					break;
				case Key.Tab:
					UserInput = _Model.ActionMgr.Complete();
					break;
				case Key.RightCtrl:
					Posture = (Posture == Posture.Launch) ? Posture.Shell : Posture.Launch;
					break;
				case Key.LeftCtrl:
					if (Posture == Posture.Launch || Posture == Posture.Shell)
					{
						_LastPosture = Posture;
						Posture = Posture.Navigation;
					}
					break;
				default:
					handled = false;
					break;
			}
			e.Handled = handled;
		}
		private void OnAnyKeyUp(object sender, KeyEventArgs e)
		{
			bool handled = true; //prevents double chars composed in WndProc
			switch (e.Key)
			{
				case Key.LeftCtrl:
					if (Posture == Posture.Navigation)
						Posture = _LastPosture;
					break;
			}
			e.Handled = handled;
		}
		private void OnPromptChanged(string prompt)
		{
			Prompt = prompt;
		}
		private void OnCurrentDirectoryChanged(string directory)
		{
			if (Posture == Posture.Navigation)			
				Prompt = CreatePrompt(directory);
		}
		private void OnActionsChanged(int current = 0, IAction[] actions = null, int dot = 0)
		{
			Actions.Clear();
			if (actions == null || actions.Length == 0)
				Actions.Add(EmptyActionViewModel);
			else
			{
				for (int index = 0; index < actions.Length; ++index)
				{
					IAction action = actions[index];
					string prompt = (index == current) ? Prompt : EMPTY_PROMPT;
					Actions.Add(new ActionViewModel(prompt, UserInput, action.Unmatched(dot), action.Description));
				}
			}
			if (ResetColumns != null)
			{
				ResetColumns();
			}
		}
		public void OnSourceInitialized(object sender, EventArgs e)
		{
			OnActionsChanged();

			_ActiveSource = PresentationSource.FromVisual(sender as Visual);
			((HwndSource)_ActiveSource).AddHook(WndProc);
			_KeyboardHook = new KeyboardHook(_ActiveSource);
			_KeyboardHook.KeyDown += OnAnyKeyDown;
			_KeyboardHook.SysKeyDown += OnAnyKeyDown;
			_KeyboardHook.KeyUp += OnAnyKeyUp;
			_KeyboardHook.SysKeyUp += OnAnyKeyUp;
			_KeyboardHook.UnhandledToHost = true;
			_KeyboardHook.MessageForwarding = MessageForwarding.Both;
		}
		public void OnClosed(object sender, EventArgs e)
		{
			_ActiveSource = PresentationSource.FromVisual(sender as Visual);
			((HwndSource)_ActiveSource).RemoveHook(WndProc);
			_KeyboardHook.KeyDown -= OnAnyKeyDown;
			_KeyboardHook.SysKeyDown -= OnAnyKeyDown;

			_Model.End();
		}
		private string CreatePrompt(string directory)
		{
			return "sa " + directory + ">";
		}
		#endregion methods
				
		#region events
		public System.Action ResetColumns;
		#endregion events

		#region fields
		delegate void OutputAppendDlg(string str, Brush color);

		private IShellAnywhereModel _Model;
		private PresentationSource _ActiveSource;
		private KeyboardHook _KeyboardHook;

		private DateTime _HiddenTime;
		#endregion fields
	}
}
